﻿using FI.BatchJob.Web.Constants;
using FI.BatchJob.Web.Data.Entities;
using FI.BatchJob.Web.Data.Entitities;
using FI.BatchJob.Web.Helper;
using FI.BatchJob.Web.Models;
using Quartz;
using Quartz.Impl;
using Quartz.Impl.AdoJobStore;
using Quartz.Impl.AdoJobStore.Common;
using Quartz.Impl.Matchers;
using Quartz.Simpl;
using Quartz.Spi;
using Quartz.Util;
using Serilog;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using static FI.BatchJob.Web.Data.Entitities.EnumType;

namespace FI.BatchJob.Web.QuartzNet
{
    /// <summary>
	///  scheduler center ,encapsulate some common operations to process the request from UI.
	/// </summary>
	public class SchedulerCenter
    {
        /// <summary>
        /// scheduler center self
        /// </summary>
        public static readonly SchedulerCenter Instance;
        static SchedulerCenter()
        {
            Instance = new SchedulerCenter();
        }

        private IDbProvider dbProvider;
        private string driverDelegateType;

        /// <summary>
        /// Set Scheduler,only when construct the instance ,will be called. 
        /// </summary>
        /// <param name="dbProvider"></param>
        /// <param name="driverDelegateType"></param>
        public void Setting(IDbProvider dbProvider, string driverDelegateType)
        {
            this.driverDelegateType = driverDelegateType;
            this.dbProvider = dbProvider;
        }


        private IScheduler _scheduler;

        /// <summary>
        ///  IScheduler
        /// </summary>
        /// <returns></returns>
        private IScheduler Scheduler
        {
            get
            {
                if (this._scheduler != null)
                {
                    return this._scheduler;
                }


                if (dbProvider == null || string.IsNullOrEmpty(driverDelegateType))
                {
                    throw new Exception("dbProvider or driverDelegateType is null");
                }

                DBConnectionManager.Instance.AddConnectionProvider("default", dbProvider);

                var serializer = new Quartz.Simpl.JsonObjectSerializer();

                serializer.Initialize();
                var jobStore = new JobStoreTX
                {
                    DataSource = "default",
                    TablePrefix = "QRTZ_",
                    InstanceId = "AUTO",
                    DriverDelegateType = driverDelegateType,
                    ObjectSerializer = serializer,

                };
                IDictionary<string, ISchedulerPlugin> plugins = new Dictionary<string, ISchedulerPlugin>();


                plugins.Add("quartz.plugin.timeZoneConverter.type", new Quartz.Plugin.TimeZoneConverter.TimeZoneConverterPlugin());

                DirectSchedulerFactory.Instance.CreateScheduler("__Scheduler__", "AUTO", new DefaultThreadPool(), jobStore, plugins, TimeSpan.FromMilliseconds(30000));
                _scheduler = SchedulerRepository.Instance.Lookup("__Scheduler__").Result;

                _scheduler.Start();//start it.

                return _scheduler;
            }
        }


        /// <summary>
        /// Adds the schedule asynchronous.
        /// </summary>
        /// <param name="entity">The entity.</param>
        /// <returns></returns>
        public async Task<ExecutionResult> AddScheduleAsync(Schedule entity)
        {

            try
            {
                //check if the task existed
                var jobKey = new JobKey(entity.JobName, entity.JobGroup);
                if (await Scheduler.CheckExists(jobKey))
                {
                    return new ExecutionResult { Code = MessageCode.IsSameExist, Message = "任务已存在" };
                }

                //reflect the type of IJob class
                var jobType = TypeHelper.BuildTypeFromAssembly(entity.AssemblyName, entity.AssemblyName + "." + entity.ClassName);
                //  var jobType = TypeHelper.BuildTypeFromAssemblyFile(@"E:\FI.BatchJob\Web\jobdllfile\FI.BatchJob.SampleTask.dll", entity.AssemblyName + "." + entity.ClassName);

                IDictionary<string, object> map = new Dictionary<string, object>();

                map.Add(Constant.ORIGINAL_SCHEDULE, entity);

                map.Add(Constant.SCHEDULE_HISTORY, new ScheduleHistory().CopyFrom(entity));



                // define the job and then, binding the class implemented IJob               
                IJobDetail job = JobBuilder.Create(jobType)
                                    .SetJobData(new JobDataMap(map))
                                    .StoreDurably(true)// store the job detail without any triggers
                                    .RequestRecovery(true)//
                                    .WithDescription(entity.JobDescription)
                                    .WithIdentity(entity.JobName, entity.JobGroup)
                                    .Build();


                // create a trigger
                ITrigger trigger;
                if (entity.TriggerType == TriggerType.CRON)
                {
                    if (!CronExpression.IsValidExpression(entity.Cron))
                    {
                        return new ExecutionResult { Code = MessageCode.CronFormatFault, Message = "CRON 表达式错误" }; ;
                    }
                    trigger = CreateCronTrigger(entity);
                }
                else
                {
                    trigger = CreateSimpleTrigger(entity);
                }

                //  make a schemdule with job and tigger.
                await Scheduler.ScheduleJob(job, trigger);

                Log.Information($"Schedule {entity.JobName} made successfully");

                return new ExecutionResult { Code = MessageCode.Success, Message = "任务添加成功" };
            }
            catch (Exception ex)
            {
                Log.Error($"failded to add a task, exception:{ex.ToString()}");
                throw;
            }

        }

        /// <summary>
        /// Pauses the job asynchronous.
        /// </summary>
        /// <param name="jobGroup">The job group.</param>
        /// <param name="jobName">Name of the job.</param>
        /// <returns></returns>
        public async Task<ExecutionResult> PauseJobAsync(string jobGroup, string jobName)
        {
            try
            {

                var jk = new JobKey(jobName, jobGroup);
                if (!await Scheduler.CheckExists(jk))
                {
                    return new ExecutionResult
                    {
                        Code = MessageCode.TaskMissing,
                        Message = jobName + "任务不存在"
                    };
                }
                await Scheduler.PauseJob(new JobKey(jobName, jobGroup));

                Log.Information($"task {jobName} pause successfully.");

                return new ExecutionResult
                {
                    Code = MessageCode.Success,
                    Message = "任务暂停成功"
                };
            }
            catch (Exception ex)
            {
                Log.Error($"faild to pause the task, exception:{ex.ToString()}");
                throw;
            };

        }


        /// <summary>
        /// Removes the job asynchronous.
        /// </summary>
        /// <param name="jobGroup">The job group.</param>
        /// <param name="jobName">Name of the job.</param>
        /// <returns></returns>
        public async Task<ExecutionResult> RemoveJobAsync(string jobGroup, string jobName)
        {
            try
            {

                var jk = new JobKey(jobName, jobGroup);
                if (!await Scheduler.CheckExists(jk))
                {
                    return new ExecutionResult
                    {
                        Code = MessageCode.TaskMissing,
                        Message = jobName + "任务不存在"
                    };
                }
                await Scheduler.DeleteJob(new JobKey(jobName, jobGroup));

                Log.Information($"task {jobName} removed successfully");

                return new ExecutionResult
                {
                    Code = MessageCode.Success,
                    Message = "任务删除成功"
                };

            }
            catch (Exception ex)
            {
                Log.Error($"faild to remove job, exception:{ex.ToString()}");
                throw;
            }

        }


        /// <summary>
        /// Resumes the job asynchronous.
        /// </summary>
        /// <param name="jobGroup">The job group.</param>
        /// <param name="jobName">Name of the job.</param>
        /// <returns></returns>
        public async Task<ExecutionResult> ResumeJobAsync(string jobGroup, string jobName)
        {
            ExecutionResult result = new ExecutionResult();
            try
            {
                //check if there is a task with same key.
                var jobKey = new JobKey(jobName, jobGroup);
                if (!await Scheduler.CheckExists(jobKey))
                {
                    return new ExecutionResult
                    {
                        Code = MessageCode.TaskMissing,
                        Message = jobName + "任务不存在"
                    };
                }
                //resume the task if exist
                await Scheduler.ResumeJob(jobKey);

                Log.Information($"task {jobName} resume to run");

                return new ExecutionResult
                {
                    Code = MessageCode.Success,
                    Message = "恢复任务成功"
                };
            }
            catch (Exception ex)
            {
                Log.Error($"failed to rusume！{ex.ToString()}");
                throw;
            }

        }


        /// <summary>
        /// Reschedules the job asynchronous.
        /// </summary>
        /// <param name="entity">The entity.</param>
        /// <returns></returns>
        /// <exception cref="Exception">cannot find the old tigger, failed to reschedule</exception>
        public async Task<ExecutionResult> RescheduleJobAsync(Schedule entity)
        {
            try
            {

                var triggerkey = await _scheduler.GetTrigger(new TriggerKey(entity.JobName, entity.JobGroup));

                if (triggerkey == null) throw new Exception("cannot find the old tigger, failed to reschedule");

                ITrigger newTrigger;
                if (entity.TriggerType == TriggerType.CRON)
                {
                    if (!CronExpression.IsValidExpression(entity.Cron))//check if the expression is correct.
                    {
                        return new ExecutionResult { Code = MessageCode.CronFormatFault, Message = "CRON 表达式错误" }; ;
                    }
                    newTrigger = CreateCronTrigger(entity);
                }
                else
                {
                    newTrigger = CreateSimpleTrigger(entity);
                }

                await _scheduler.RescheduleJob(triggerkey.Key, newTrigger);

                return new ExecutionResult
                {
                    Code = MessageCode.Success,
                    Message = "任务修改成功"
                };
            }
            catch (Exception ex)
            {
                Log.Error($"failed to reschedule, exception:{ex.ToString()}");
                throw;
            }
        }

        /// <summary>Checks the trigger exist.</summary>
        /// <param name="jobGroup">The job group.</param>
        /// <param name="jobName">Name of the job.</param>
        /// <returns></returns>
        public async Task<bool> CheckTriggerExist(string jobGroup, string jobName)
        {
            JobKey jobKey = new JobKey(jobName, jobGroup);

            var triggersList = await Scheduler.GetTriggersOfJob(jobKey);

            return triggersList.Any();

        }




        /// <summary>Queries the schedule asynchronous.</summary>
        /// <param name="jobKey">The job key.</param>
        /// <returns></returns>
        public async Task<Schedule> QueryScheduleAsync(JobKey jobKey)
        {
            var entity = new Schedule();

            var jobDetail = await Scheduler.GetJobDetail(jobKey);
            var triggersList = await Scheduler.GetTriggersOfJob(jobKey);
            var trigger = triggersList.AsEnumerable().FirstOrDefault();//default there is only a trigger.

            entity.JobName = jobKey.Name;
            entity.JobGroup = jobKey.Group;
            entity.JobDescription = jobDetail.Description;


            var original_entity = jobDetail.JobDataMap.Get(Constant.ORIGINAL_SCHEDULE) as Schedule;


            entity.TriggerType = original_entity.TriggerType;
            entity.Interval = original_entity.Interval;
            entity.RepeatTimes = original_entity.RepeatTimes;
            entity.Cron = original_entity.Cron;

            entity.AssemblyName = original_entity.AssemblyName;
            entity.ClassName = original_entity.ClassName;
            entity.Params = original_entity.Params;


            entity.BeginTime = original_entity.BeginTime;
            entity.EndTime = original_entity.EndTime;


            if (trigger == null)//tigger compeleted or unset
            {
                entity.PreviousFireTime = null;
                entity.NextFireTime = null;
                entity.TriggerState = TriggerState.None;
            }
            else
            {

                entity.BeginTime = trigger.StartTimeUtc.LocalDateTime;
                entity.EndTime = trigger.EndTimeUtc?.LocalDateTime;
                entity.PreviousFireTime = trigger.GetPreviousFireTimeUtc()?.LocalDateTime;
                entity.NextFireTime = trigger.GetNextFireTimeUtc()?.LocalDateTime;
                entity.TriggerState = await Scheduler.GetTriggerState(trigger.Key);

            }

            return entity;
        }

        public async Task<bool> TriggerJobAsync(JobKey jobKey)
        {
            await Scheduler.TriggerJob(jobKey);
            return true;
        }

        /// <summary>Gets all jobs asynchronous.</summary>
        /// <returns></returns>
        public async Task<List<Schedule>> GetAllJobAsync()
        {
            List<JobKey> jobKeyList = new List<JobKey>();

            List<Schedule> scheduleList = new List<Schedule>();

            var groupNames = await Scheduler.GetJobGroupNames();

            foreach (var groupName in groupNames.OrderBy(t => t))
            {
                jobKeyList.AddRange(await Scheduler.GetJobKeys(GroupMatcher<JobKey>.GroupEquals(groupName)));

            }
            foreach (var jobKey in jobKeyList.OrderBy(t => t.Name))
            {
                var jobDetail = await Scheduler.GetJobDetail(jobKey);
                var triggersList = await Scheduler.GetTriggersOfJob(jobKey);

                var entity = new Schedule
                {
                    JobName = jobKey.Name,
                    JobGroup = jobKey.Group,
                    JobDescription = jobDetail.Description
                };

                var original_entity = jobDetail.JobDataMap.Get(Constant.ORIGINAL_SCHEDULE) as Schedule;

                if (original_entity != null)
                {

                    entity.TriggerType = original_entity.TriggerType;
                    entity.Interval = original_entity.Interval;
                    entity.RepeatTimes = original_entity.RepeatTimes;
                    entity.Cron = original_entity.Cron;

                    entity.AssemblyName = original_entity.AssemblyName;
                    entity.ClassName = original_entity.ClassName;
                    entity.Params = original_entity.Params;


                    entity.BeginTime = original_entity.BeginTime;
                    entity.EndTime = original_entity.EndTime;
                }

                var trigger = triggersList.AsEnumerable().FirstOrDefault();


                if (trigger == null)//compeleted 
                {
                    entity.PreviousFireTime = null;
                    entity.NextFireTime = null;
                    entity.TriggerState = TriggerState.None;
                }
                else
                {
                    entity.BeginTime = trigger.StartTimeUtc.LocalDateTime;
                    entity.EndTime = trigger.EndTimeUtc?.LocalDateTime;
                    entity.PreviousFireTime = trigger.GetPreviousFireTimeUtc()?.LocalDateTime;
                    entity.NextFireTime = trigger.GetNextFireTimeUtc()?.LocalDateTime;
                    entity.TriggerState = await Scheduler.GetTriggerState(trigger.Key);

                }

                scheduleList.Add(entity);

            }
            return scheduleList;
        }



        public async Task<bool> StartScheduleAsync()
        {
            //start 
            if (Scheduler.InStandbyMode)
            {
                await Scheduler.Start();

                Log.Information("scheduler started............");
            }
            //LoggingJobHistoryPlugin history = new LoggingJobHistoryPlugin();
            //add a global listener.
            Scheduler.ListenerManager.AddJobListener(new JobListener(this.dbProvider), GroupMatcher<JobKey>.AnyGroup());

            return Scheduler.InStandbyMode;
        }


        /// <summary>Stops the schedule asynchronous.</summary>
        /// <returns></returns>
        public async Task<bool> StopScheduleAsync()
        {
            //判断调度是否已经关闭
            if (!Scheduler.InStandbyMode)
            {
                //等待任务运行完成
                await Scheduler.Standby(); //TODO  注意：Shutdown后Start会报错，所以这里使用暂停。
                Log.Information("任务调度暂停！");
            }
            return !Scheduler.InStandbyMode;
        }

        /// <summary>Creates the simple trigger.</summary>
        /// <param name="entity">The entity.</param>
        /// <returns></returns>
        private ITrigger CreateSimpleTrigger(Schedule entity)
        {

            if (entity.RepeatTimes != -1)
            {
                return TriggerBuilder.Create()
               .WithIdentity(entity.JobName, entity.JobGroup)
               .StartAt(entity.BeginTime)
               .EndAt(entity.EndTime)
               .WithSimpleSchedule(x =>
               {
                   x.WithIntervalInSeconds(entity.Interval)
                        .WithRepeatCount(entity.RepeatTimes)
                        .WithMisfireHandlingInstructionNextWithRemainingCount();
                   //.WithMisfireHandlingInstructionFireNow();
               })
               .ForJob(entity.JobName, entity.JobGroup)
               .Build();
            }
            else//if the repeatTimes =-1,run the job forever.
            {
                return TriggerBuilder.Create()
               .WithIdentity(entity.JobName, entity.JobGroup)
               .StartAt(entity.BeginTime)
               .EndAt(entity.EndTime)
               .WithSimpleSchedule(x =>
               {
                   x.WithIntervalInSeconds(entity.Interval)
                        .RepeatForever()
                        .WithMisfireHandlingInstructionNextWithRemainingCount();
               })
               .ForJob(entity.JobName, entity.JobGroup)
               .Build();
            }

        }


        /// <summary>Creates the cron trigger.</summary>
        /// <param name="entity">The entity.</param>
        /// <returns></returns>
        private ITrigger CreateCronTrigger(Schedule entity)
        {

            return TriggerBuilder.Create()

                   .WithIdentity(entity.JobName, entity.JobGroup)
                   .StartAt(entity.BeginTime)
                   .EndAt(entity.EndTime)
                   .WithCronSchedule(entity.Cron, cronScheduleBuilder => cronScheduleBuilder.WithMisfireHandlingInstructionDoNothing())
                   .ForJob(entity.JobName, entity.JobGroup)
                   .Build();
        }

    }
}



